package org.NooLab.glue.components;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

import org.NooLab.glue.MessageBoard;
import org.NooLab.glue.msgframe.XRegistration;
import org.NooLab.glue.recdispatcher.ReceptorLink;
import org.NooLab.glue.recdispatcher.ReceptorLinks;
import org.NooLab.glue.subscription.Future;
import org.NooLab.glue.subscription.FutureIntf;
import org.NooLab.glue.subscription.FutureProperties;
import org.NooLab.glue.subscription.FuturesIntf;
import org.NooLab.glue.transaction.Transaction;

import org.NooLab.utilities.logging.PrintLog;
import org.NooLab.utilities.nums.NumUtilities;
import org.NooLab.utilities.strings.ArrUtilities;

/**
 * 
 * The ClientMatcher object is transported as an Interface via the "MessageBoardFactoryProperties"
 * 
 * it is a plugin structure for the MessageBoard and can be used as a callback of that
 * to custom rules for the matching of producers and consumer, which are used inside
 * the MessageBoard.
 * The MessageBoard takes the ClientMatcher as an argument of its constructor.
 * 
 * There is (of course) no possibility for the "clients" (who register at the MessageBoard) to
 * change these rules, they are completely server-based,
 * 
 * yet, the ClientMatcher refers also to the FilterPackage (if there is one), and these
 * FilterPackages can be transferred as part of data transfer;
 * such the SOURCEs can determine which (kind of) clients will take which message  
 *
 */
public class ClientMatcher implements ClientMatcherIntf{

	MessageBoard messageBoard;
	
	ReceptorLinks receptorLinks;
	
	Vector<Integer> activePorts = null;
	
	
	int respectAssignments;
	
	
	ArrUtilities arrutil = new ArrUtilities();
	NumUtilities numutil = new NumUtilities();
	PrintLog out;
	
	
	
	public ClientMatcher( PrintLog outprn){
		out = outprn;  
	}
	
	
	
	public void setMessageBoard(MessageBoard messageboard){
		messageBoard = messageboard;
		
		receptorLinks = messageBoard.getReceptorLinks() ;
		
		activePorts = receptorLinks.getActivatedPorts();
	}
	


	@Override
	public boolean contentTypesMatching(String cType1, String cType2) {

		boolean rB=false;
		String str,vstr;
		String[] ctypes1=new String[1], ctypes2=new String[1];
		Vector<String> cts1, cts2;
		
		cType1 = cType1.replace(";", ",");
		cType2 = cType2.replace(";", ",");
		
		if (cType1.indexOf(",") > 0) {
			ctypes1 = cType1.split(",");
		} else {
			ctypes1[0] = cType1.trim();
		}
		
		if (cType2.indexOf(",") > 0) {
			ctypes2 = cType2.split(",");
		} else {
			ctypes2[0] = cType2.trim();
		}

		
		cts1 = new Vector<String>(Arrays.asList(ctypes1));
		cts2 = new Vector<String>(Arrays.asList(ctypes2));
		
		
		for (int k = 0; k < cts1.size(); k++) {

			str = cts1.get(k);

			for (int j=0;j<cts2.size();j++){
				vstr = cts2.get(j) ;
				
				rB = vstr.contains(str) || (str.contentEquals("*")) || (vstr.contentEquals("*"));
				if (rB){ break; }
			}
			
			if (rB){ break; }
		}
		
		
		return rB;
	}

	/**
	 * 
	 * this is essentially the same as findMatchingParticipant/s(), just that we check 
	 * a particular potential receptor match  
	 * 
	 */
	@Override
	public long checkMatchingOfParticipant( Transaction transaction, long proposedIID) {
		 
		
		
		return -1;
	}



	/**
	 * the transaction knows about the matching criteria;
	 * it searches in both directions a receptor for a source, and the source for a receptor in order return the receipt/state 
	 * @param transaction
	 * @return
	 */
	synchronized public long[] findMatchingParticipants( Transaction transaction, int respectAssignments ) {
		long[] matchingIIDs = new long[0];
		 
		Vector<Long>  viids = new Vector<Long> ();
		long iid, siid = -1;
		ReceptorLink item;
		int port = -1, cport=-1, regState,p ;
		
		int linkrel, relItemID = -1, relGroupID=-1 ;
		
		String contentType , coparticipant="", roleOfinitializer ;
		
		XRegistration xreg ;
		
		
		Vector<XRegistration> candidates = new Vector<XRegistration>();  
		 
		// ......................................
		
		this.respectAssignments = respectAssignments;
		
		if (activePorts==null){
			// activePorts = receptorLinks.getActivatedPorts();
		}
		
		linkrel =  transaction.getLinkRelation();
		
		if (linkrel<=0){
			linkrel=1;
		}
		relItemID = transaction.getRelationItemID() ;
		relGroupID = transaction.getRelationGroupID() ;
		
		roleOfinitializer = transaction.getRoleOfInitializer().toLowerCase() ;
		
		coparticipant = getCoParticipant(roleOfinitializer); // returns empty list ???
				
		contentType = transaction.getContentType().trim();
		
		iid = transaction.getInstanceID() ;
		candidates = identifyCandidates( coparticipant , contentType, iid);
		
		checkforLinkRelations( linkrel, relItemID, relGroupID, coparticipant, candidates );
		
												out.print(4,"Client Matcher - identified candidates : "+ candidates.size());
	
		Map<Integer,Integer> portFrequencies = new HashMap<Integer,Integer>();
		Map<Integer,Long> portsOfInstances = new HashMap<Integer,Long> ();
		
												out.print(3, "based on doctype, "+candidates.size()+" candidates have been found...");
		// create a mapping of iid -> ports
	    p=0;
		for (int k=0;k<receptorLinks.size();k++){
			xreg = candidates.get(k);
			portsOfInstances.put( xreg.getClientListeningPort(), xreg.getInstanceID() ) ;
		}
												
		int minfreq = 9999999; 
		if (respectAssignments<=0){
			
			if (receptorLinks!=null){
				
				for (int k=0;k<receptorLinks.size();k++){
					item = receptorLinks.get(k);
					port = item.getReceptorPort() ;
					
					if (portFrequencies.containsKey(port)==false){
						portFrequencies.put(port, 1);
					}else{
						portFrequencies.put(port, 1 + portFrequencies.get(port));
					}
					
				} // k->
			
			}
			
			for (Map.Entry<Integer, Integer> entry : portFrequencies.entrySet()) {
				// System.out.println("Key : " + entry.getKey()  " Value : " + entry.getValue());
				if ((minfreq > entry.getValue()) && (entry.getKey()!=null) && (entry.getKey()>0)){
					minfreq = entry.getValue();
					cport = entry.getKey() ;
					siid = portsOfInstances.get(cport) ;
				}
		    }
			
			// is any of the candidates not yet in the field -> priority !
			for (int i=0;i<candidates.size();i++){
				xreg = candidates.get(i) ;
				port = xreg.getClientListeningPort() ;
				if ( (portFrequencies.containsKey(port)==false)){
					cport = port;
					siid = xreg.getInstanceID() ;
					break;
				}
			}
		
												out.print(4, "... port "+cport+" has been preselected...");
		} // respectAssignments<=0 ?
		
		// we put the preferred instance to first position 0
		if (siid>0){
			viids.add( siid );
		}
		
		port = -1;
		for (int i=0;i<candidates.size();i++){
			
			xreg = candidates.get(i) ;
			
			// we simply look for the first not being null...
			if (xreg!=null){
				port = xreg.getClientListeningPort() ;
				regState = xreg.getReceptorState() ;
				
				if ((respectAssignments>=1) || (minfreq == 9999999) || (cport<=10)){
					// here we simply take the first candidate which is not allocated, or in case we did not find a port in our "receptorLinks"
					if (regState < XRegistration.RST_IS_ALLOCATED){

						iid = xreg.getInstanceID();
						if (viids.indexOf(iid)<0){
							viids.add( iid );
						}
					}
				}else{
					// here, in contrast, work under the assumption of a dedicated balancing mechanism, so we
					// we have to touch all of the candidates...  we take that one, which is least frequently used so far  
					// XRegistration.RST_IS_RECOGNIZED
					
					// TODO
					
					if ((cport==xreg.getClientListeningPort()) ){ 
						if (regState < XRegistration.RST_IS_RECOGNIZED){

							iid = xreg.getInstanceID();
							if (viids.indexOf(iid)<0){
								viids.add( iid );
							}
						}
						
					}
				}
				
				
			}// xreg=null?
		}
		  
		
		matchingIIDs = arrutil.changeArrayStyle( viids, 0);
												String str = "s have"; if (matchingIIDs.length==1){ str ="has"; }
												out.print(3, "... "+matchingIIDs.length+" port"+str+" been selected finally.");
		
		return matchingIIDs;
	}
	
	synchronized public int findMatchingParticipant( Transaction transaction, int respectAssignments ) {
		
		int port = -1;
		int[] ports ;
		long[] iids ;
		XRegistration xreg;
		
		Vector<XRegistration> registeredInstances ;
		
		
		registeredInstances = messageBoard.getMsgBoardShares().getMetaRegistration().getRegisteredInstances() ;
		
		iids = findMatchingParticipants( transaction, respectAssignments);
		if (iids.length>0){
			
			// here the balancing should be performed
			xreg = XRegistration.getItemByID( iids[0], registeredInstances);
			
			xreg.setReceptorState( XRegistration.RST_IS_ALLOCATED ) ;
			
			port = (XRegistration.getItemByID( iids[0], registeredInstances )).getClientListeningPort() ;
		}
												out.print(3, "... port "+port+" has been selected finally.");		
		return port;
	}

	
	private  Vector<XRegistration> checkforLinkRelations( int linkrel, int relItemID, int relGroupID,
														  String coparticipant,
														  Vector<XRegistration> candidates ){
		
		boolean hb, shouldReturnReceipt,returnsReceipt ;
		int xlr;
		XRegistration xreg;
		
		
		if (candidates.size()>0 ){
			
			// linkRelations etc. are properties of "InstanceFlavor" 
			
			
			shouldReturnReceipt = (numutil.isOdd(linkrel)==false) && (linkrel>0) ;
			
			for (int i=0;i<candidates.size();i++){
				
				xreg = candidates.get(i) ;
				xlr = xreg.getSrlinkRelation();
				
				returnsReceipt = (xlr>0) && (numutil.isOdd(xlr)==false) ;
				hb = false;
				
				if ( (returnsReceipt==shouldReturnReceipt) || 
				     ( (returnsReceipt==false) && (shouldReturnReceipt==true))){
					
					hb = linkRelationMatchesR( coparticipant, linkrel, xlr);
					
					if (hb==false){
						candidates.set(i, null) ;
					}
					
				} // returnsReceipt==shouldReturnReceipt ?

				// linkrel, relItemID = -1, relGroupID
			
			}// i-> all candidates
			
		}// portCollection ?
		
		
		return candidates;
	}
	
	private boolean linkRelationMatchesR( String coparticipant, int linkrel, int xlr){
		
		boolean rB = true;
		
		if ( linkrel >= xlr ){
			rB = true;
			
			 
			if (coparticipant == "RECEPTOR") {
				
				 
					if (linkrel > 1) {

						if (numutil.isWithin(linkrel, 1, 2, 1)) {

						}

						if (numutil.isWithin(linkrel, 3, 4, 1)) {
							// check for instanceID of RECEPTOR 
							// --- obj/xml field is missing everywhere ...
							// creating registration, reading + establishing xRegistration 
							
						}

						if (numutil.isWithin(linkrel, 5, 6, 1)) {

						}
					}
					
				 
			} // coparticipant == "RECEPTOR" ?
		 
		}else{ // else::if linkrel >= xlr == (linkrel < xlr) ?
			rB = false;
		}
		return rB;
	}
	
	
	private String getCoParticipant( String roleOfinitializer){
		String coparticipant = "";
		
		if (roleOfinitializer.contains("source")) {
			coparticipant = "RECEPTOR";
		}
		if (roleOfinitializer.contains("receptor")) {
			coparticipant = "SOURCE";
		}
		
		return coparticipant;
	}
	
	private boolean xregItemIsMatching( XRegistration xreg, String coparticipant){
		boolean rB=false;
		
		if ((coparticipant.length()==0) || (xreg.getRole().contentEquals(coparticipant))){ // e.g. "RECEPTOR" ?
			rB=true;
		}
		return rB ;
	}
	

	@SuppressWarnings("unused")
	private Vector<XRegistration> identifyCandidates( String coparticipant, String contentType, long excludedIID ){

		String str;
		boolean hb = false ;
		int port, linkrel = -1,regState;

		XRegistration xreg;

		Vector<String> vstr;
		Vector<String> cts;

		String[] ctypes = new String[1];
		String[] strs = new String[1];

		Vector<XRegistration> candidates = new Vector<XRegistration>();

		if (contentType.indexOf(",") > 0) {
			ctypes = contentType.split(",");
		} else {
			ctypes[0] = contentType;
		}
		cts = new Vector<String>(Arrays.asList(ctypes));

		
		// search through all registered instances
		for (int i = 0; i < messageBoard.getRegisteredInstances().size(); i++) {

			xreg = messageBoard.getRegisteredInstances().get(i);
			hb = xreg.isInstanceTested() ;
			
			if ((xreg.getInstanceID() <= 0) || (hb==false)){
				// isInstanceTested is set through the ConnectionsManager, which supervises
				// the connections and shuts down unreachable ones...
				// yet, we still need to check the immediate response for the transfer of data
				continue;
			}

			// in principle, empty values are allowed for coparticipant !
			if (xregItemIsMatching(xreg, coparticipant)) {

				str = xreg.getAcceptType();

				if (str.indexOf(",") > 0) {
					strs = str.split(",");
				} else {
					strs[0] = str;
				}
				vstr = new Vector<String>(Arrays.asList(strs));

				
				for (int k = 0; k < vstr.size(); k++) {
					str = vstr.get(k);
					hb = (cts.contains(str)) || (str.contentEquals("*"));
					if (hb) {
						port = xreg.getClientListeningPort();
						if (candidates.contains(xreg) == false) {
							
							if ((receptorIsBusy(xreg)==false) || (respectAssignments <=0 )){
								if ((xreg.getInstanceID() != excludedIID) || (excludedIID<=0)){
									candidates.add(xreg);
								}
							}
						}

					}// k-> all in vstr
				}


				if (hb == false) {
					for (int k = 0; k < cts.size(); k++) {

						str = cts.get(k);

						hb = vstr.contains(str) || (str.contentEquals("*"));  
						if ((hb) && (linkrel > 0)) {
							// break;
							// there could be several, and we have further
							// criteria...
							port = xreg.getClientListeningPort();
							hb = true;
						}else{
							hb = false;
						}
					} // k-> all in cts
				} // hb still false ?

				if (hb) {
					 
					out.print(4,"Client Matcher - identifyCandidates, receptorIsBusy = "+receptorIsBusy(xreg)+" : "+xreg);
				}

				
				if ((hb) && 
					( (receptorIsBusy(xreg)==false) || (respectAssignments <=0 ))){
					if (candidates.contains(xreg) == false) {
						if ((xreg.getInstanceID() != excludedIID) || (excludedIID<=0)){
							candidates.add(xreg);
						}
					}
				}
			} // role = "RECEPTOR" ?

		} // i-> all registered instances
		return candidates;
	}

 
	private boolean receptorIsBusy( XRegistration xreg ){
		boolean   isBusy;
		int regState ;
		
		regState = xreg.getReceptorState() ;
		
		isBusy = ( regState >= XRegistration.RST_IS_RUNNING) || 
        				 ( regState == XRegistration.RST_IS_ALLOCATED );
		  
		return isBusy ;
	}
	
}
